/*
 * Coder
 *
 *  Created on: 10/06/2011
 *      Author: Agustina I. Stekolschik & Ezequiel Palumbo
 */
#include <Coder.h>
#include <LevelContext.h>
#include <Logging.h>

using logging::Logger;
using logging::LoggerFactory;
using logging::Level;

namespace coder{

const EndOfFileDataUnit eofDataUnit;
//split the integer
const unsigned int g_FirstQuarter = 0x20000000;
const unsigned int g_ThirdQuarter = 0x60000000;
const unsigned int g_Half         = 0x40000000;


ArithmeticCoder::ArithmeticCoder(){
	//current boundaries
	this->lowBound = 0;
	this->topBound = 0x7FFFFFFF;

	this->currScale = 0;
	this->currStep = 0;

}

void ArithmeticCoder::encode(ContextUnit& aContextUnit){
	// Obtengo frecuencias menores a la mia :)
	Logger& logger = LoggerFactory::getInstance().getLogger("Model.Arithmetic.Coder");

	logger.log(logging::TRACE, "Starting the encoding process.");
	AbstractContext& ctxOwner = aContextUnit.getOwnerContext();

	unsigned int lowBoundary = 0;
	unsigned int topBoundary = 0;
	unsigned int totalFrequency = ctxOwner.getTotalFrequency();

	logger.log(logging::TRACE, "Getting the low boundary.");
	bool foundUnit = false;
	unsigned int position = 0;
	ContextUnit* positionUnit;
	char auxCharArray[] = {0,0,0,0};
	while (!foundUnit && position < ctxOwner.getUnits().size()){
		positionUnit = ctxOwner.getUnits().at(position);
		if (positionUnit->getDataUnit() == aContextUnit.getDataUnit()){
			foundUnit = true;
			logger.log(logging::TRACE, "The DataUnit encoded: ");
                        for (int i=0 ; i < sizeof(auxCharArray) ; i++) {
                        	auxCharArray[i] = 0;
                        }
			sprintf(auxCharArray, "%d", positionUnit->getDataUnit().asInt());
			logger.log(logging::TRACE, auxCharArray);
		}else{
			lowBoundary += positionUnit->getFrequency();
			position++;
		}
	}
	logger.log(logging::TRACE, "Finished with the boundaries.");

	positionUnit = ctxOwner.getUnits().at(position);
	topBoundary = lowBoundary + positionUnit->getFrequency();
	this->encode(lowBoundary, topBoundary, totalFrequency);
	logger.log(logging::TRACE, "Finished with the encode.");
	if(positionUnit->getDataUnit() == eofDataUnit){
		logger.log(logging::TRACE, "Encoding End Of File.");
		this->encodeFinish();
		logger.log(logging::TRACE, "End Of File encoded.");
	}
}

void ArithmeticCoder::setDataOutput(BitDataOutput& dataOutput){
	this->bitDataOutput = &dataOutput;
}


void ArithmeticCoder::encode(const unsigned int low_count, const unsigned int high_count, const unsigned int total ){
	Logger& logger = LoggerFactory::getInstance().getLogger("Model.Arithmetic.Coder");
	logger.log(logging::TRACE, "Starting the range encoding process.");

	logger.log(logging::TRACE, "Set the current scale");
	this->currStep = ( this->topBound - this->lowBound + 1 ) / total;

	logger.log(logging::TRACE, "Update boundaries.");
	this->topBound = this->lowBound + this->currStep * high_count - 1;
	this->lowBound = this->lowBound + this->currStep * low_count;

	logger.log(logging::TRACE, "Update buffer");
	while( ( this->topBound < g_Half ) || ( this->lowBound >= g_Half ) ){
		if( this->topBound < g_Half ){
			this->bitDataOutput->writeBit( 0 );
			this->lowBound = this->lowBound * 2;
			this->topBound = this->topBound * 2 + 1;

			for(; this->currScale > 0; this->currScale-- )
				this->bitDataOutput->writeBit(1 );
		}else if( this->lowBound >= g_Half ){
			this->bitDataOutput->writeBit(1);
			this->lowBound = 2 * ( this->lowBound - g_Half );
			this->topBound = 2 * ( this->topBound - g_Half ) + 1;

			for(; this->currScale > 0; this->currScale-- )
				this->bitDataOutput->writeBit(0 );
		}
	}

	logger.log(logging::TRACE, "Updates buffer");
	while( ( g_FirstQuarter <= this->lowBound ) && ( this->topBound < g_ThirdQuarter ) ){
		this->currScale++;
		this->lowBound = 2 * ( this->lowBound - g_FirstQuarter );
		this->topBound = 2 * ( this->topBound - g_FirstQuarter ) + 1;
	}
}

void ArithmeticCoder::encodeFinish(){
	Logger& logger = LoggerFactory::getInstance().getLogger("Model.Arithmetic.Coder");
	logger.log(logging::TRACE, "Complete with bits to save the full encoded number.");

	if( this->lowBound < g_FirstQuarter ){
		this->bitDataOutput->writeBit(0 );
		for( int i=0; i<this->currScale+1; i++ )
			this->bitDataOutput->writeBit(1);
	}else{
		this->bitDataOutput->writeBit(1 );
	}
	this->bitDataOutput->complete();
}


ArithmeticCoder::~ArithmeticCoder(){

}

}
